package com.ccteam.fluidmusic.fluidmusic.common.repositories.impl

import android.content.Context
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import androidx.datastore.dataStore
import com.ccteam.fluidmusic.common.datastore.protobuf.CurrentPlayingProtos
import com.ccteam.fluidmusic.fluidmusic.common.model.CurrentPlayingModel
import com.google.android.exoplayer2.Player.REPEAT_MODE_OFF
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream

/**
 * @author Xiaoc
 * @since 2020.1.23
 *
 * 基于Proto的DataStore仓库
 * 该仓库用于使用DataStore-Proto进行存储从而代替 [SharedPreferences]
 *
 * 而Proto就是存储类的对象（typed objects）
 * 通过 protocol buffers 将对象序列化存储在本地
 * 具体内容见 https://mp.weixin.qq.com/s/tT7TxUt2IMC4UVwuwMCBrA
 * 简而言之，DataStore更安全也更快速，使用Kotlin Flow机制
 * 而 Proto 使用了二进制编码压缩，体积更小，速度比 XML 更快，且比Json更快速
 *
 * 该类为单例，使用 Hilt 进行管理
 * @see com.ccteam.fluidmusic.fluidmusic.common.di.RepositoryModule.provideProtoDataStoreRepository
 */
private const val currentPlayingDataStoreFileName = "currentPlaying.pb"

/**
 * 当前播放内容存储的DataStore
 */
private val Context.currentPlayingDataStore by dataStore(
    fileName = currentPlayingDataStoreFileName,
    serializer = CurrentPlayingSerializer
)

/**
 * [CurrentPlayingProtos.CurrentPlaying] 的序列化器
 * 为什么要写这个是因为Java自带序列化用了耗费资源性能的反射
 * 而Android利用该序列化器将其显式调用防止耗费多余性能
 */
@Suppress("BlockingMethodInNonBlockingContext")
private object CurrentPlayingSerializer: Serializer<CurrentPlayingProtos.CurrentPlaying>{
    override val defaultValue: CurrentPlayingProtos.CurrentPlaying
        get() = CurrentPlayingProtos.CurrentPlaying.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): CurrentPlayingProtos.CurrentPlaying {
        return withContext(Dispatchers.IO){
            try {
                // 是编译器自动生成的，用于读取并解析 input 的消息
                return@withContext CurrentPlayingProtos.CurrentPlaying.parseFrom(input)
            } catch (e: Exception){
                throw CorruptionException("无法读取Proto内容 ${e.message}")
            }
        }

    }

    override suspend fun writeTo(t: CurrentPlayingProtos.CurrentPlaying, output: OutputStream) {
        // t.writeTo(output) 是编译器自动生成的，用于写入序列化消息
        withContext(Dispatchers.IO){
            t.writeTo(output)
        }
    }

}

class ProtoDataStoreRepository(
    private val context: Context
) {

    /**
     * 存储当前播放内容
     * 其中可能包括当前播放的位置，模式等等
     * @param currentPlayingModel 当前播放内容模型
     */
    suspend fun saveCurrentPlaying(currentPlayingModel: CurrentPlayingModel){
        context.currentPlayingDataStore.updateData { currentPlaying ->
            currentPlaying.toBuilder()
                .setQueueId(currentPlayingModel.currentQueueId)
                .setPosition(currentPlayingModel.currentPosition)
                .setRepeatMode(currentPlayingModel.currentRepeatMode)
                .setShuffleMode(currentPlayingModel.currentShuffleMode)
                .build()
        }
    }

    /**
     * 读取当前播放内容
     * 基于Flow，且捕获了异常，不会出现空指针的问题
     */
    fun readCurrentPlaying(): Flow<CurrentPlayingModel>{
        return context.currentPlayingDataStore.data.map {
            CurrentPlayingModel(it.queueId,it.position,it.repeatMode,it.shuffleMode)
        }.catch {
                if(it is IOException){
                    it.printStackTrace()
                    emit(CurrentPlayingModel(0,0,REPEAT_MODE_OFF,false))
                } else {
                    throw it
                }
            }
    }

}